#include "Client.hpp"

namespace MRMDS::Client
{
MRMDS::Message::BasicValue PackInt(int val)
{
    MRMDS::Message::BasicValue bv;
    bv.set_int32(val);
    return bv;
}
MRMDS::Message::BasicValue PackFloat(float val)
{
    MRMDS::Message::BasicValue bv;
    bv.set_float_(val);
    return bv;
}
MRMDS::Message::BasicValue PackDouble(double val)
{
    MRMDS::Message::BasicValue bv;
    bv.set_double_(val);
    return bv;
}
MRMDS::Message::BasicValue PackString(std::string val)
{
    MRMDS::Message::BasicValue bv;
    bv.set_string(val);
    return bv;
}

using boost::asio::ip::tcp;
void Client::Connect(std::string ip, int port)
{
    tcp::resolver resolver(ioctx);
    auto endpoints = resolver.resolve(ip, std::to_string(port));
    socket = std::make_unique<tcp::socket>(ioctx);
    auto endPoint = boost::asio::connect(*socket, endpoints);
}
void Client::Disconnect()
{
    socket->close();
}

template <class T> void Client::SendRequest(T req)
{
    MRMDS::Message::Frame frame;
    google::protobuf::Any *any = new google::protobuf::Any();
    any->PackFrom(req);
    frame.set_allocated_data(any);
    auto timestamp = new google::protobuf::Timestamp();
    auto now = std::chrono::system_clock::now();
    double seconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
    double nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count() - seconds * 1e9;
    timestamp->set_seconds(seconds);
    timestamp->set_nanos(nanos);
    frame.set_allocated_timestamp(timestamp);

    int size = frame.ByteSizeLong();
    if (!frame.SerializePartialToArray(buffer.begin() + 4, buffer.size() - 4))
    {
        throw "PackBuffer overflow";
    }
    buffer[3] = size | 0xFF;
    size >>= 8;
    buffer[2] = size | 0xFF;
    size >>= 8;
    buffer[1] = size | 0xFF;
    size >>= 8;
    buffer[0] = size | 0xFF;
    size = size + 4;

    socket->send(boost::asio::buffer(buffer.begin(), size));
}

void Client::ReceiveResponse(MRMDS::Message::Response &rep)
{
    MRMDS::Message::Frame frame;
    socket->receive(boost::asio::buffer(buffer.begin(), 4));
    int len = buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3];
    int size = socket->receive(boost::asio::buffer(buffer.begin(), len));

    if (!frame.ParseFromArray(buffer.begin(), size))
    {
        throw "unable to decode response";
    }
    frame.data().UnpackTo(&rep);
}

bool Client::LogIn(std::string passward, std::string name)
{
    MRMDS::Message::LoginReq req;
    req.set_name(name);
    req.set_passward(passward);

    MRMDS::Message::Response rep;
    InvokeRequest(req, rep);

    if (rep.code() == MRMDS::Message::Response::ResponseCode::Response_ResponseCode_Success)
    {
        id = rep.values().at(0).int32();
        return true;
    }
    return false;
}

bool Client::LogOut()
{
    MRMDS::Message::LogoutReq req;
    req.set_id(id);

    MRMDS::Message::Response rep;
    InvokeRequest(req, rep);
    if (rep.code() == MRMDS::Message::Response::ResponseCode::Response_ResponseCode_Success)
    {
        return true;
    }
    return false;
}

bool Client::GetInstanceList(int filter, std::map<int, std::string> &instances)
{
    MRMDS::Message::GetInstancesReq req;
    req.set_filter(filter);

    MRMDS::Message::Response rep;
    InvokeRequest(req, rep);
    if (rep.code() != MRMDS::Message::Response::ResponseCode::Response_ResponseCode_Success)
    {
        return false;
    }
    instances.clear();
    for (auto pair : rep.values())
    {
        instances[pair.first] = pair.second.string();
    }
    return true;
}
bool Client::GetValue(std::vector<int> ids, std::vector<int> keys, std::map<int, std::map<int, VariantValue>> &values)
{
    MRMDS::Message::GetValueReq req;
    for (int id : ids)
    {
        req.add_id(id);
    }
    for (int key : keys)
    {
        req.add_key(key);
    }

    MRMDS::Message::Response rep;
    InvokeRequest(req, rep);
    if (rep.code() != MRMDS::Message::Response::ResponseCode::Response_ResponseCode_Success)
    {
        return false;
    }

    values.clear();
    for (int i = 0; i < ids.size(); i++)
    {

        int id = ids[i];
        for (auto pair : rep.values())
        {
            switch (pair.second.RealValue_case())
            {
            case MRMDS::Message::BasicValue::kInt32:
                values[id][pair.first] = pair.second.int32();
                break;
            case MRMDS::Message::BasicValue::kFloat:
                values[id][pair.first] = pair.second.float_();
                break;
            case MRMDS::Message::BasicValue::kDouble:
                values[id][pair.first] = pair.second.double_();
                break;
            case MRMDS::Message::BasicValue::kString:
                values[id][pair.first] = pair.second.string();
                break;
            default:
                throw "undefien Message";
            }
        }
    }
    return true;
}

bool Client::SetValue(std::map<int, VariantValue> &values)
{
    MRMDS::Message::SetValueReq req;
    for (auto valuepair : values)
    {
        switch (valuepair.second.index())
        {
        case 0:
            req.mutable_values()->insert({valuepair.first, PackInt(std::get<int>(valuepair.second))});
            break;
        case 1:
            req.mutable_values()->insert({valuepair.first, PackFloat(std::get<float>(valuepair.second))});
            break;
        case 2:
            req.mutable_values()->insert({valuepair.first, PackDouble(std::get<double>(valuepair.second))});
            break;
        case 3:
            req.mutable_values()->insert({valuepair.first, PackString(std::get<std::string>(valuepair.second))});
            break;
        }
    }

    MRMDS::Message::Response rep;
    InvokeRequest(req, rep);
    if (rep.code() != MRMDS::Message::Response::ResponseCode::Response_ResponseCode_Success)
    {
        return true;
    }
    return false;
}

} // namespace MRMDS::Client